package com.example.csmall.product.filter;

import com.alibaba.fastjson.JSON;
import com.example.csmall.product.security.LoginPrincipal;
import com.example.csmall.product.web.JsonResult;
import com.example.csmall.product.web.ServiceCode;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

@Slf4j
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    @Value("${csmall.jwt.secret-key}")
    String secretKey;

    public static final Integer JWT_MIN_LENGTH=133;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        //清空SecurityContext
        //SecurityContextHolder.clearContext();

        //获取请求头中名为Authroization的Json数据，客户端发送的jwt通常名字是Authorization
        String jwt = request.getHeader("Authorization");
        log.debug("尝试接收用户端携带的JWT数据=>{}",jwt);

        //判断jwt的基础有效性，
        //StringUtils.hasText判断jwt是否存在文本
        //jwt.length()判断jwt是否小于最小长度
        if(!StringUtils.hasText(jwt) || jwt.length()<133){
            //当jwt的基本有效性不通过时，放行，因为可能存在访问secrity白名单中请求的可能
            filterChain.doFilter(request,response);
            return;
        }

        response.setContentType("application/json; charset=utf-8;");
        //客户端携带了基本有效的jwt，开始尝试解析jwt
        Claims claims = null;
        try{
         claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
        }catch(ExpiredJwtException e){
            String message = "您登录的信息已经过期";
            JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_EXPIRED, message);
            String s = JSON.toJSONString(jsonResult);
            PrintWriter pw = response.getWriter();
            pw.println(s);
            return;
        }catch(SignatureException e){
            String message = "非法的访问";
            JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_SIGNATURE, message);
            String s = JSON.toJSONString(jsonResult);
            PrintWriter pw = response.getWriter();
            pw.println(s);
            return;
        }catch (MalformedJwtException e) {
            String message = "非法访问！";
            JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_MALFORMED, message);
            String jsonResultString = JSON.toJSONString(jsonResult);
            PrintWriter writer = response.getWriter();
            writer.println(jsonResultString);
            writer.close();
            return;
        } catch (Throwable e) {
            String message = "服务器忙，请稍后再次尝试！（开发过程中，如果看到此提示，请检查控制台的信息，并在JWT过滤器补充处理此异常）";
            JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_UNKNOWN, message);
            String jsonResultString = JSON.toJSONString(jsonResult);
            PrintWriter writer = response.getWriter();
            writer.println(jsonResultString);
            writer.close();
        }
        Long id = claims.get("id",Long.class);
        String username = claims.get("username",String.class);
        String authoritiesJsonString = claims.get("authoritiesJsonString",String.class);
        log.debug("从JWT中解析出来的管理员id:{}",id);
        log.debug("从JWT中解析出来的管理员username:{}",username);
        log.debug("从JWT中解析出来的管理员authoritiesJsonString:{}",authoritiesJsonString);

        //基于解析JWT中的信息的结果创建Authentication

        LoginPrincipal loginPrincipal = new LoginPrincipal();
        loginPrincipal.setId(id);
        loginPrincipal.setUsername(username);
        Object principal = loginPrincipal;    //当事人： 暂时使用JWT中的username属性值
        Object credentials = null;  //凭证信息：此处信息应该是在处理登陆时使用，此处应该为null
        List<SimpleGrantedAuthority> authorities
                = JSON.parseArray(authoritiesJsonString, SimpleGrantedAuthority.class); //获取权限信息
        //根据上述准备好的信息创建authentication对象
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                principal,credentials,authorities
        );

        //将authentication(身份验证存在SecurityContext当中)
        SecurityContext securityContext = SecurityContextHolder.getContext();
        securityContext.setAuthentication(authentication);

        // 当前程序状态，当请求的信息不携带jwt数据时，不直接允许这个请求通过，
        // 之后再由Security的其他过滤器判断此请求是否在白名单当中，是否允许这个请求通过
        // 当登录成功后，获得到正确的jwt信息，此时浏览器请求时通过携带正确的jwt数据，此拦截器中就可以对jwt信息进行解析
        // 并且通过解析传来的数据创建出authentication(身份验证)对象，
        // 并且将创建好的authentication对象存入到Spring secrity对象当中
        // 因为当服务器没有重启时并且用户的请求已经通过上述的认证的情况下，就算不携带jwt也可以直接访问到服务器中的各种请求
        // 这时因为就算不携带正确的请求头在Spring securpty的SecurityContext中已经存放的此用户的信息
        // 并且Spring security的securityContext还是基于Session实现的，所以就算不发送正确的jwt来请求
        // 在一段时间内也是可以正确的发送访问的
        // 当然这样重启服务器后，第一期发起的请求就没有携带JWT，如果此请求不在Security白名单中的话，就会返回403错误
        // 过滤器链继续执行(当只有一个过滤器的时候就相当于放行了)
        filterChain.doFilter(request,response);
    }

}
